package emulator.hardware.nmos6502.stack;

import java.util.Observable;

import emulator.EmulatorException;
import emulator.hardware.HwByte;
import emulator.hardware.HwWord;
import emulator.hardware.bus.Bus;
import emulator.hardware.debug.BusWatchException;
import emulator.hardware.memory.UnmappedMemoryException;
import emulator.hardware.nmos6502.Flags6502;

public class CpuStack extends Observable {
	
	private static final int STACK_BASE = 0x0100;
	public static final int STACK_SIZE = 256;
	
	private Bus bus = null;
	private StackData[] stack_data = null;
	private int sp;
	
	public CpuStack()
	{
		stack_data = new StackData [STACK_SIZE];
		for (int i = 0; i < STACK_SIZE; i++)
			stack_data[i] = new StackData();
	}
	
	public void setBus(Bus bus) {
		this.bus = bus;
	}

	public int getSP() { return sp; }
	public HwByte getSPByte() { return new HwByte(sp); }
	public void setSP(int sp) { this.sp = (sp & 0xFF); }
	public void setSP(HwByte sp) { this.sp = (int) sp.getNumber(); }
	
	public void pushAddress(int word, int source) throws BusWatchException, UnmappedMemoryException, StackOverflowException {
		setCurrentSource(source);
		push((word >> 8) & 0xFF, StackData.DATA_ADDRESS_HIGH);
		push(word & 0xFF, StackData.DATA_ADDRESS_LOW);
	}

	public void pushByte(int data, int type) throws BusWatchException, UnmappedMemoryException, StackOverflowException {
		setCurrentSource(StackData.SOURCE_UNKNOWN);
		push(data, type);
	}
	
	public int popAddress() throws UnmappedMemoryException, BusWatchException, StackUnderflowException {
		int lo = pop();
		int hi = pop();
		return lo | (hi << 8);
	}

	public int popByte() throws UnmappedMemoryException, BusWatchException, StackUnderflowException {
		return pop();
	}

	private void push(int data, int type) throws BusWatchException, UnmappedMemoryException, StackOverflowException
	{
		if (sp == 0)
			throw new StackOverflowException();
		setCurrentType(type);
		bus.write(STACK_BASE + sp, data);
		sp = (sp - 1) & 0xff;
	}

	private int pop() throws BusWatchException, UnmappedMemoryException, StackUnderflowException
	{
		if (sp == 0xff)
			throw new StackUnderflowException();
		sp = (sp + 1) & 0xff;
		return bus.read(STACK_BASE + sp);
	}

	private void setCurrentType(int type) {
		stack_data[sp].setType(type);
	}

	private void setCurrentSource(int source) {
		stack_data[sp].setSource(source);
	}

	public String topOfStackToString() throws EmulatorException
	{
		String result = "";
		try {
			int sp_adr = STACK_BASE + sp + 1;
			for (int i = 0; i < 6 && sp_adr < STACK_BASE + STACK_SIZE; i++, sp_adr++)
				result += new HwByte(bus.read(sp_adr)) + " ";
		} catch (UnmappedMemoryException e) {
			result += "??";
		}
		return result;
	}
	
	public int countEntries()
	{
		int count = 0;
		for (int i = sp+1; i < STACK_SIZE; i++)
			if (stack_data[i].getType() != StackData.DATA_ADDRESS_LOW)
				count++;
		return count;
	}
	
	public StackEntry[] getContents() throws EmulatorException
	{
		StackEntry[] result = new StackEntry [countEntries()];
		int current_index = 0;
		int address = 0;
		for (int i = sp+1; i < STACK_SIZE; i++)
		{
			if (stack_data[i].getType() == StackData.DATA_ADDRESS_LOW)
				address = bus.read(STACK_BASE+i);
			else if (stack_data[i].getType() == StackData.DATA_ADDRESS_HIGH)
				result[current_index++] = new StackEntry(i-1, stack_data[i].getSource(), new HwWord(address | bus.read(STACK_BASE+i) << 8));
			else if (stack_data[i].getType() == StackData.DATA_FLAGS)
				result[current_index++] = new StackEntry(i, stack_data[i].getSource(), new Flags6502(bus.read(STACK_BASE + i)));
			else
				result[current_index++] = new StackEntry(i, stack_data[i].getSource(), new HwByte(bus.read(STACK_BASE + i)));
		}
		assert(current_index == result.length);
		return result;
	}
	
	public int countFrames()
	{
		int count = 0;
		for (int i = sp+1; i < STACK_SIZE; i++)
			if (stack_data[i].getSource() != StackData.SOURCE_UNKNOWN)
				count++;
		return count;
	}
}
